-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local mdb_service = require 'mc.mdb.mdb_service'
local log = require 'mc.logging'

local ETH_OBJ_PATH<const> = '/bmc/kepler/Managers/1/EthernetInterfaces'
local ETH_OBJ_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces'
local ETH_PORT_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.MgmtPort'
local ETH_GROUP_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.EthGroup'

local m = {}
local mdb_setting = {
    IsValidId = false,
    GroupId = 0,
    EthernetInterfaces = {
        Path = "",
        Interface = "",
        PortId = "PortId",
        VLANEnable = "VLANEnable"
    },
    Ipv4 = {
        Path = "",
        Interface = ""
    },
    Ipv6 = {
        Path = "",
        Interface = "",
        IpMode = "IpMode",
        IpAddr = "IpAddr",
        DefaultGateway = "DefaultGateway",
        SetIpAddr = "SetIpAddr",
        SetDefaultGateway = "SetDefaultGateway" 
    },
    NetworkConfig = {
        mode = "",
        portId = 0,
        vlanEnable = "",
        vlanId = 0
    }
}

-- 用于web页面和资源树类型的相互转化
local type_switch_table = {
    mdb_to_web = {
        Lom = 'LOM',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        Ocp = 'OCP',
        Ocp2 = 'OCP2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    },
    web_to_mdb = {
        LOM = 'Lom',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        OCP = 'Ocp',
        OCP2 = 'Ocp2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    }
}

local function get_netinterface_object(mdb, bus, mdb_path, interface_name)
    local ok, netinterface_object = pcall(function()
        return mdb.get_object(bus, mdb_path, interface_name)
    end)
    if not ok then
        return nil
    end
    return netinterface_object
end

local function get_mac_patched_ethgroup_object(mdb, bus, mdb_path, interface_name, mac_id)
    local netinterface_object = get_netinterface_object(mdb, bus, mdb_path, interface_name)
    if netinterface_object ~= nil and netinterface_object.OutType == 2 then
        local net_mac = netinterface_object.Mac
        if net_mac == mac_id then
            return netinterface_object
        end
    end
    return nil
end

-- 获取和当前mac地址匹配的资源树对象，若没有匹配上就返回nil 
local function get_mac_patched_eth_object(mdb, bus, mdb_path, interface_name, mac_id)
    local netinterface_object = get_netinterface_object(mdb, bus, mdb_path, interface_name)

    if netinterface_object ~= nil then
        local net_mac = netinterface_object.Mac
        log:notice("[multi_patch] netmac = %s, macid = %s", net_mac, mac_id)
        if net_mac == mac_id then
            return netinterface_object
        end
    end

    return nil
end

-- 获取和当前port_id匹配的资源树对象，若没有匹配上就返回nil 
local function get_port_id_patched_eth_object(mdb, bus, mdb_path, interface_name, port_id)
    local netinterface_object = get_netinterface_object(mdb, bus, mdb_path, interface_name)
    if netinterface_object ~= nil then
        if port_id == netinterface_object.ActivePortId then
            return netinterface_object
        end
    end
    return nil
end

local function patch_mac_to_ethgroup(mdb, bus, mdb_path, interface_name, mac_id)
    local ok, rsp = pcall(mdb_service.get_sub_paths, bus, mdb_path .. "/EthGroup", 1, {interface_name})

    if not ok then
        log:debug('Incorrect parent path or interface, path = %s, interface = %s', mdb_path, interface_name)
        return false
    end
    for _, sub_path in pairs(rsp.SubPaths) do
        log:notice("[multi_patch] patch_mac_to_ethgroup sub_path is %s", sub_path)
        local ethgroup_object = get_mac_patched_ethgroup_object(mdb, bus, sub_path, interface_name, mac_id)
        if ethgroup_object ~= nil then
            if ethgroup_object.OutType == 2 and ethgroup_object.Status then
                mdb_setting.IsValidId = true
                mdb_setting.GroupId = ethgroup_object.GroupId
                mdb_setting.EthernetInterfaces.Path = sub_path
                mdb_setting.EthernetInterfaces.Interface = "bmc.kepler.Managers.EthernetInterfaces.EthGroup"
                mdb_setting.EthernetInterfaces.PortId = "ActivePortId"
                mdb_setting.EthernetInterfaces.VLANEnable = "VLANEnabled"
                mdb_setting.Ipv4.Path = sub_path
                mdb_setting.Ipv4.Interface = "bmc.kepler.Managers.EthernetInterfaces.EthGroup"
                mdb_setting.Ipv6.Path = sub_path 
                mdb_setting.Ipv6.Interface = "bmc.kepler.Managers.EthernetInterfaces.EthGroup"
                mdb_setting.Ipv6.IpMode = "Ipv6Mode" 
                mdb_setting.Ipv6.IpAddr = "Ipv6Addr"
                mdb_setting.Ipv6.DefaultGateway = "Ipv6DefaultGateway"
                mdb_setting.Ipv6.SetIpAddr = "SetIpv6Addr"
                mdb_setting.Ipv6.SetDefaultGateway = "SetIpv6DefaultGateway"
                return true
            end
        end
    end

    mdb_setting.IsValidId = false
    return false
end

local function patch_mac_to_eth(mdb, bus, mdb_path, interface_name, mac_id)
    local netinterface_object = get_mac_patched_eth_object(mdb, bus, mdb_path, interface_name, mac_id)
    if netinterface_object ~= nil and netinterface_object.Status then
        mdb_setting.IsValidId = true
        mdb_setting.GroupId = 0
        mdb_setting.EthernetInterfaces.Path = mdb_path
        mdb_setting.EthernetInterfaces.Interface = "bmc.kepler.Managers.EthernetInterfaces"
        mdb_setting.Ipv4.Path = mdb_path .. "/Ipv4"
        mdb_setting.Ipv4.Interface = "bmc.kepler.Managers.EthernetInterfaces.Ipv4"
        mdb_setting.Ipv6.Path = mdb_path .. "/Ipv6"
        mdb_setting.Ipv6.Interface = "bmc.kepler.Managers.EthernetInterfaces.Ipv6"
        return true
    end
    return false
end

function m.patch_eth_info(mac_id)
    local mdb_path = "/bmc/kepler/Managers/1/EthernetInterfaces"
    local interface_name = "bmc.kepler.Managers.EthernetInterfaces"
    if mac_id == nil then
        mdb_setting.IsValidId = true
        mdb_setting.GroupId = 0
        mdb_setting.EthernetInterfaces.Path = mdb_path
        mdb_setting.EthernetInterfaces.Interface = "bmc.kepler.Managers.EthernetInterfaces"
        mdb_setting.Ipv4.Path = mdb_path .. "/Ipv4"
        mdb_setting.Ipv4.Interface = "bmc.kepler.Managers.EthernetInterfaces.Ipv4"
        mdb_setting.Ipv6.Path = mdb_path .. "/Ipv6"
        mdb_setting.Ipv6.Interface = "bmc.kepler.Managers.EthernetInterfaces.Ipv6"
        return mdb_setting
    end

    -- 检查是否符合旧情况：符合Interfaces的Mac 
    if patch_mac_to_eth(mdb, bus, mdb_path, interface_name, mac_id) then
        return mdb_setting
    end
    -- 检查是否符合组信息挂载在EthGroup子目录下的情况
    if patch_mac_to_ethgroup(mdb, bus, mdb_path, "bmc.kepler.Managers.EthernetInterfaces.EthGroup", mac_id) then
        return mdb_setting
    end
    mdb_setting.IsValidId = false
    return mdb_setting
end

local function all_port_automatic(port_objs, mdb_setting_array, ethernet_object)
    -- 先处理OriginalGroupId为0的网口对象
    for _, port_o in pairs(port_objs) do
        if port_o.OriginalGroupId == 0 and port_o.CurrentGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
    end
    -- 再处理OriginalGroupId不为0的网口对象
    for _, port_o in pairs(port_objs) do
        if port_o.OriginalGroupId ~= 0 then
            port_o.CurrentGroupId = 0
        end
    end

    local set_network_config_para = {}
    set_network_config_para.path = ETH_OBJ_PATH
    set_network_config_para.interface = ETH_OBJ_INTERFACE
    set_network_config_para.NetworoConfig = {}
    set_network_config_para.NetworoConfig.mode = 'Automatic'
    set_network_config_para.NetworoConfig.portId = ethernet_object.PortId
    set_network_config_para.NetworoConfig.vlanEnable = ethernet_object.VLANEnable
    set_network_config_para.NetworoConfig.vlanId = ethernet_object.VLANId

    table.insert(mdb_setting_array, set_network_config_para)
end

local function switch_port_to_fixed(port_o, mdb_setting_array, ethernet_object, eth_group_objs)
    -- 网口回到自己的网口组
    port_o.CurrentGroupId = port_o.OriginalGroupId

    local set_network_config_para = {}
    set_network_config_para.NetworoConfig = {}
    local group_obj = nil
    if port_o.OriginalGroupId == 0 then
        group_obj = ethernet_object
        set_network_config_para.path = ETH_OBJ_PATH
        set_network_config_para.interface = ETH_OBJ_INTERFACE
        set_network_config_para.NetworoConfig.vlanEnable = group_obj.VLANEnable
        log:notice("switch port to fixed, ethernet type, portid:%s", port_o.Id)
    else
        for _, eth_group_obj in pairs(eth_group_objs) do
            if eth_group_obj.GroupId == port_o.OriginalGroupId then
                group_obj = eth_group_obj
            end
        end
        if group_obj == nil then
            return
        end
        set_network_config_para.path = group_obj.path
        set_network_config_para.interface = ETH_GROUP_INTERFACE
        set_network_config_para.NetworoConfig.vlanEnable = group_obj.VLANEnabled
        log:notice("switch port to fixed, eth group type, portid:%s", port_o.Id)
    end

    set_network_config_para.NetworoConfig.mode = 'Fixed'
    set_network_config_para.NetworoConfig.portId = port_o.Id
    set_network_config_para.NetworoConfig.vlanId = group_obj.VLANId

    table.insert(mdb_setting_array, set_network_config_para)
end

-- 此方法只修改多网口组端口数据 
function m.patch_network_ports(network_ports, net_mode)
    local port_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local ethernet_object = get_netinterface_object(mdb, bus, ETH_OBJ_PATH, ETH_OBJ_INTERFACE)
    local mdb_setting_array = {}

    -- 自动模式，全部网口对象都归属到Group0
    if net_mode == 'Automatic' then
        all_port_automatic(port_objs, mdb_setting_array, ethernet_object)
        return mdb_setting_array
    end
    local eth_group_objs = mdb.get_sub_objects(bus, ETH_OBJ_PATH .. "/EthGroup", ETH_GROUP_INTERFACE)

    local type, device_port_id
    for _, network_port in pairs(network_ports) do
        type = network_port.Type
        device_port_id = network_port.PortNumber
        for _, port_o in pairs(port_objs) do
            if device_port_id == port_o.DevicePortId and tostring(type) == port_o.Type then
                switch_port_to_fixed(port_o, mdb_setting_array, ethernet_object, eth_group_objs)
            end
        end
    end

    return mdb_setting_array
end

return m